# -*- coding: utf-8 -*-

import psi, psi.process
import os
import re
import sys
import cherrypy
import math
import time
import traceback
import smtplib
import datetime
import pprint
import gc
import rrdtool
from threading import Thread, Timer
from Cheetah.Template import Template
from email.MIMEMultipart import MIMEMultipart
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email import Encoders

checkedService = {}
memoryInfo = {'total': '', 'used': '', 'free': ''}
_1m = 1024*1024
intervalTimer = 5
_1day = 86400
_30days = 2592000
_90days = 7776000
gmail_user = "monitor@mana.vn"
gmail_pwd = "monitor@)!!"
rrdDb = 'dashboard.rrd'
st = int(time.time()) - intervalTimer
if not os.path.isfile(rrdDb):
    rrdtool.create(rrdDb, 
       '--start', str(st),
       '--step', str(intervalTimer),
       'DS:memstat:GAUGE:{0}:U:U'.format(2*intervalTimer),
       'DS:cpustat:GAUGE:{0}:U:U'.format(2*intervalTimer),
       'RRA:AVERAGE:0.5:1:3600',
       'RRA:AVERAGE:0.5:6:3600')
    
def getProcess(services=None):
    print 'call getProcess'
    listProcesses = []
    try:
        print "There are {0} processes".format(len(psi.process.ProcessTable()))
        for p in psi.process.ProcessTable().values():
            try:
                cmdLine = ' '.join(p.args).strip()
            except psi.AttrInsufficientPrivsError, e:
                cmdLine = str(e)
            if services != None:
                for s in services.values():
                    if ((s != p.name) or (re.search(s, cmdLine) == None)): continue 
            all_status = [(s, getattr(psi.process, s)) for s in dir(psi.process)
                          if s.startswith('PROC_STATUS_')]
            proc_status = [s[0] for s in all_status if p.status == s[1]]
            pstatus = ', '.join(proc_status)
            listProcesses.append({'pid': int(p.pid), 'name': p.name, 'exe': getattr(p, 'exe', 'N/A'),
                    'cmdLine': cmdLine, 'status': int(p.status), 'statusName': pstatus, 'memUse': math.ceil(p.rss/_1m)})
        print 'finished getProcess'
    except:
        traceback.print_exc()
    finally:
        return listProcesses

def memoryStatistic():
    try:
        m = re.findall('(\d+)', os.popen("free -m | grep Mem").read())
        if m: 
            memoryInfo['total'] = long(m[0])
            memoryInfo['used'] = long(m[1])
            memoryInfo['free'] = long(m[2])
    except:
        traceback.print_exc()

def mail(to, subject, text, attach=None):
    ''' Dùng Gmail để gửi thông tin alert đi'''
    print 'call mail({0}, {1}, {2})'.format(to, subject, text)
    try:
        msg = MIMEMultipart()
        msg['From'] = gmail_user
        msg['To'] = to
        msg['Subject'] = subject
        msg.attach(MIMEText(text))
        if attach != None:
           part = MIMEBase('application', 'octet-stream')
           part.set_payload(open(attach, 'rb').read())
           Encoders.encode_base64(part)
           part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(attach))
           msg.attach(part)
        mailServer = smtplib.SMTP("smtp.gmail.com", 587)
        mailServer.ehlo()
        mailServer.starttls()
        mailServer.ehlo()
        mailServer.login(gmail_user, gmail_pwd)
        mailServer.sendmail(gmail_user, to, msg.as_string())
        mailServer.close()
    except:
        traceback.print_exc()

def getCpu():
    a = os.popen("iostat").read()
    cbreak = False
    cpu = 'U'
    for line in a.splitlines():
        if cbreak:
            m = re.search('([0-9\.]+)', line)
            if m: cpu = m.group(1)
            break
        if line.startswith('avg-cpu'): cbreak = True
    return float(cpu)

def checkProcess(services):
    print 'call checkProcess'
    listProcesses = getProcess(services)
    for name, s in services.items(): checkedService[s] = checkedService[s] = {'desc': name, 'result': False, 'pid': 'N/A', 'name': s, 'cmdLine': '', 
                    'statusName': 'Not Found', 'status': '', 'memUse': ''}
    for p in listProcesses:
        c = 0
        for s, v in checkedService.items():
            if v['result']: continue
            c += 1
            if re.search("({0}\s)|({0}$)".format(s), p['cmdLine']):
                if p['status'] in [0, 1]:
                    checkedService[s]['result'] = True
                else:
                    checkedService[s]['result'] = False
                checkedService[s]['pid'] = p['pid']
                checkedService[s]['name'] = p['name']
                checkedService[s]['cmdLine'] = p['cmdLine']
                checkedService[s]['statusName'] = p['statusName']
                checkedService[s]['status'] = p['status']
                checkedService[s]['memUse'] = p['memUse']
        if c==0: break
    memoryStatistic()
    ctime = int(time.time())
    rrdtool.update(rrdDb, "--template", "memstat:cpustat", "%d:%d:%f" % (ctime, memoryInfo['free'], getCpu()))
    start_time = int(time.time()) - 720
    ve('images/60p_memory.png', 3600, 'memstat', {'vertical-label': 'Memory (MB)', 'title': 'Time'})
    ve('images/1d_memory.png', _1day, 'memstat', {'vertical-label': 'Memory (MB)', 'title': 'Time'})
    ve('images/30d_memory.png', _30days, 'memstat', {'vertical-label': 'Memory (MB)', 'title': 'Time'})
    ve('images/90d_memory.png', _90days, 'memstat', {'vertical-label': 'Memory (MB)', 'title': 'Time'})
    ve('images/60p_cpu.png', 3600, 'cpustat', {'vertical-label': 'CPU (%)', 'title': 'Time'})
    ve('images/1d_cpu.png', _1day, 'cpustat', {'vertical-label': 'CPU (%)', 'title': 'Time'})
    ve('images/30d_cpu.png', _30days, 'cpustat', {'vertical-label': 'CPU (%)', 'title': 'Time'})
    ve('images/90d_cpu.png', _90days, 'cpustat', {'vertical-label': 'CPU (%)', 'title': 'Time'})
    del listProcesses
    Timer(intervalTimer, checkProcess, args=[services]).start()
    
def makeReport(sendMail=False):
    while True:
        print 'makeReport'
        errorList = []
        if memoryInfo['free'] < 100: errorList.append({'name': 'Memory', 'reason': 'Too low: {0}'.format(memoryInfo['free'])})
        for p in checkedService.values():
            if p['result'] == False:
                reason = 'Not running' if p['pid'] != 'N/A' else 'Not found'
                errorList.append({'name': p['desc'], 'reason': reason})
        if len(errorList) > 0:
            now = str(datetime.datetime.now())
            t = Template(file='mail.template.html', searchList=[{'errors': errorList, 'time': now}])
            pprint.pprint(str(t))
            if sendMail: mail('giangnh@mana.vn', 'Mornitoring server at {0}'.format(now), str(t))
        time.sleep(intervalTimer)

def ve(filename, start_time, dsName, info={}):
    print 'call ve'
    ctime = int(time.time())
    rrdtool.graph(filename,
                  '--start', str(ctime - start_time),
                  '--end', str(ctime),
                  '--vertical-label', info['vertical-label'],
                  '--imgformat', 'PNG',
                  '--title', info['title'],
                  '--lower-limit', '0',
                  'DEF:data={0}:{1}:AVERAGE'.format(rrdDb, dsName),
                  'LINE1:data#FF0000') 

def startWebServer():
    cherrypy.config.update({'server.socket_port':6688, 
        'server.socket_host':'0.0.0.0', 
        'engine.autoreload.on': True, 
        'tools.sessions.on': True,
        'tools.encode.encoding': 'utf-8',
        'server.environment': 'development'})
    conf = {'/images': {'tools.staticdir.dir': os.path.join(currentDir, 'images'),
                        'tools.staticdir.on': True,
                        'tools.staticdir.content_types': {'png': 'image/png'}}}
    cherrypy.config.update({'sessionFilter.on': True})
    cherrypy.log.error_log.propagate = False
    cherrypy.log.access_log.propagate = False
    cherrypy.quickstart(Mornitor(), '/', config=conf)

class Mornitor():
    
    @cherrypy.expose
    def showForm(self):
        return '''
        <form name='f' action='/submit'>
            <input type='text' name='a' />
            <br />
            <input type='text' name='b' />
            <br />
            <input type='submit' value='Submit'/>
        </form>
        '''
        
    @cherrypy.expose
    def submit(self, a, b):
        return "a={0}, b={1}".format(a, b)
        
    @cherrypy.expose
    def index(self):
        try:
            _avg = psi.loadavg()
            loadAvg = '({0}, {1}, {2})'.format(round(_avg[0], 2), round(_avg[1], 2), round(_avg[2], 2))
            refreshMeta = "<meta http-equiv='refresh' content='{0}' />".format(intervalTimer)
            t = Template(file="report.template.html", searchList=[{'processList':checkedService, 
                        'refreshMeta': refreshMeta, 'freeMemory': memoryInfo['free'], 'loadAvg':loadAvg, 
                        'img': {'cpu': "{0}?nocache={1}".format('images/cpu.png', time.time()), 
                        'memory': "{0}?nocache={1}".format('images/memory.png', time.time())}}])
            gc.collect()
            del gc.garbage[:]
            return str(t)
        except:
            return str(sys.exc_info())
    
    @cherrypy.expose
    def list(self):
        try:
            listProcesses = getProcess()
            listProcesses = sorted(listProcesses, key=lambda p:p['memUse'], reverse=True)
            t = Template(file="process_list.template.html", searchList=[{'processList':listProcesses, 'refreshMeta': ''}])
            gc.collect()
            del gc.garbage[:]
            return str(t)
        except:
            return str(sys.exc_info())

if __name__ == '__main__':
    currentDir = os.path.dirname(os.path.abspath(__file__))
    checks = {'Apache service': 'httpd', 
              'Sphinx searchd': 'searchd', 
              'MySQL': 'mysqld', 
              'MongoDB': 'mongod', 
              'beanstalkd': 'beanstalkd',
              'crond': 'crond', 
              'Front end server': 'python.+ server.py', 
              'Restart worker service': 'python.+restart-service.py',
              'Module Tien len': 'python.+worker.py.+tienlenmn.py',
              'Module Tai game': 'python.+worker.py.+taigame.py',
              'Module Rao vat': 'python.+worker.py.+raovat.py',
              'Module Mail': 'python.+worker.py.+mail.py',
              'Mana News': 'python.+worker.+partner.py',
              'Cac module trong Cfg1.py': 'python.+worker.py.+cfg1.py'
              }
    Thread(target=checkProcess(checks)).start()
    Thread(target=makeReport).start()
    startWebServer()
#    Thread(target=startWebServer).start()
    
    
    
